#!/usr/bin/env python
# -*- coding: utf-8 -*-

import gtk

import struct
import array

import pluginmanager
import dialogs

__version__ = "1.0 Final"
__author__ = "Diego Hansen Hahn"

@pluginmanager.register('FORTE-SCAN')
def mega_scan(*args, **kwargs):

    fd = kwargs['infile']

    fd.seek(0xA0, 0)
    if not fd.read(12) == 'MEGAMAN&BASS':
        dialogs.Error(None, 'megaforte.py\n\nA ROM informada não é suportada por esse plugin.')
        return []

    number = 356
    base_address = 0x64BA38
    results = []

    fd.seek(0x5079D8, 0)
    for x in range(number):
        results.append((base_address + struct.unpack('<L', fd.read(4))[0], 'FORTE'))
        fd.read(4) #Pula segunda flag do header

        while gtk.events_pending():
            gtk.main_iteration()

    return results

@pluginmanager.register('FORTE-POINTER')
def mega_pointer(*args, **kwargs):
    fd = kwargs['infile']
    old_address = kwargs['old_address']
    new_address = kwargs['new_address']
    csize = kwargs['csize']
    usize = kwargs['usize']

    entries = 356
    base_address = 0x64BA38
    rel = old_address - base_address

    fd.seek(0x5079D8, 0)
    for x in range(entries):
        if struct.unpack('<L', fd.read(4))[0] == rel:
            fd.seek(-4,1)
            break
        fd.seek(4,1)

    return (struct.pack('<LL', new_address - base_address, usize), fd.tell())

@pluginmanager.register('FORTE-UNCOMPRESS')
def mega_decoder(*args, **kwargs):
    try:
        address = kwargs['address']
        fd = kwargs['infile']

        number = 356
        base_address = 0x64BA38
        rel = address - base_address
        output = array.array('c')

        fd.seek(0x5079D8, 0)
        for x in range(number):
            if struct.unpack('<L', fd.read(4))[0] == rel:
                break
            fd.read(4)

        data_size = struct.unpack('<L', fd.read(4))[0]

        fd.seek(address, 0)
        while len(output) < data_size:
            flag = struct.unpack('B', fd.read(1))[0]
            for x in range(8):
                if ((flag << x) & 0x80) == 0:
                    output.append(fd.read(1))
                else:
                    byte1 = struct.unpack('B', fd.read(1))[0]
                    byte2 = struct.unpack('B', fd.read(1))[0]
                    size = byte1 >> 2
                    distance = ((byte1 & 3) << 8) | byte2
                    for x in range(size):
                        output.append(output[len(output)  - distance])
                if len(output) >= data_size:
                    break

        return output
    except:
        return False

@pluginmanager.register('FORTE-COMPRESS')
def mega_encoder(*args, **kwargs):
    '''
    Recebe um arquivo de entrada (infile), comprime em lzss e retorna o resultado em um array de caracteres.
    '''
    def search(pattern, text):
        ''' Boyer-Moore-Horspool - Copyright Nelson Rush '''
        m = len(pattern)
        n = len(text)
        if m > n:
            return -1
        skip = []
        for k in range(256):
            skip.append(m)
        for k in range(m - 1):
            skip[ord(pattern[k])] = m - k - 1
        skip = tuple(skip)
        k = m - 1
        while k < n:
            j = m - 1
            i = k
            while j >= 0 and text[i] == pattern[j]:
                j -= 1
                i -= 1
            if j == -1:
                return (i + 3, m) # Distância, Tamanho
            k += skip[ord(text[k])]
        return -1

    def encode_buffer(buffer):

        flag = 0
        coded_buffer = array.array('c')

        for x in range(len(buffer)):
            if isinstance(buffer[x], tuple):
                data = ""
                data += struct.pack('B', ((buffer[x][1] << 2) | (buffer[x][0] >> 8)))
                data += struct.pack('B', (buffer[x][0] & 0xFF))
                coded_buffer.extend(data)
                flag |= (1 << 7 - x)
            else:
                coded_buffer.extend(buffer[x])
        coded_buffer.insert(0, chr(flag))
        return coded_buffer

    infile = kwargs['infile']

    buffer = array.array('c')

    uncoded_lookahead = array.array('c')

    # Poderia ter usado o deque do collections para fazer a pilha (sliding_window).
    # mas, aparentemente, ficou mais lento usando ele...
    sliding_window = array.array('c')

    sliding_window_size = 0x3FF
    max_search_size = 0x3F

    to_code_buffer = []

    infile.seek(0,0)
    # Inicia preenchendo sliding_window com 2 bytes
    for x in infile.read(2):
        sliding_window.insert(0, x)
        to_code_buffer.append(x)
    while True:
        pattern = infile.read(3)
        if len(pattern) != 3:
            for x in pattern:
                if len(to_code_buffer) == 8:
                    buffer.extend(encode_buffer(to_code_buffer))
                    to_code_buffer = []
                to_code_buffer.append(x)
            buffer.extend(encode_buffer(to_code_buffer))
            buffer.extend('\x00'*(len(buffer) % 4))
            return buffer
        for x in pattern:
            uncoded_lookahead.insert(0, x)
        #Adiciona o primeiro byte do padrão na janela deslizante
        sliding_window.insert(0, pattern[0])
        s_result = search(uncoded_lookahead, sliding_window)
        if s_result == -1: #Não foi achado o padrão na janela
            infile.seek(-2, 1)
            to_code_buffer.append(pattern[0])
            uncoded_lookahead = array.array('c')
        else: # Foi achado. Será buscado na janela um padrão maior (de até 18 bytes de tamanho)
            settings = None
            while True:
                c = infile.read(1)
                if not c: # Se não houver mais bytes, adiciona os parâmetros da busca anterior
                    to_code_buffer.append(s_result)
                    buffer.extend(encode_buffer(to_code_buffer))
                    buffer.extend('\x00'*(len(buffer) % 4))
                    return buffer
                uncoded_lookahead.insert(0, c)
                sliding_window.insert(0, uncoded_lookahead[2])
                settings = s_result # Guarda o resultado anterior em uma variável
                s_result = search(uncoded_lookahead, sliding_window)
                if s_result == -1:
                    infile.seek(-1, 1)
                    to_code_buffer.append(settings)
                    sliding_window.insert(0, uncoded_lookahead[1])
                    uncoded_lookahead = array.array('c')
                    break
                elif len(uncoded_lookahead) == max_search_size:
                    to_code_buffer.append(s_result)
                    sliding_window.insert(0, uncoded_lookahead[1])
                    sliding_window.insert(0, uncoded_lookahead[0])
                    uncoded_lookahead = array.array('c')
                    break

        while len(sliding_window) > sliding_window_size:
            sliding_window.pop()

        if len(to_code_buffer) == 8:
            buffer.extend(encode_buffer(to_code_buffer))
            to_code_buffer = []
